package ru.stepanovdn.trading.server.service.processor

import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import ru.stepanovdn.trading.server.entity.Candle
import ru.stepanovdn.trading.server.entity.Order
import ru.stepanovdn.trading.server.service.notification.NotificationService
import ru.stepanovdn.trading.server.service.order.OrderService
import ru.stepanovdn.trading.server.service.price.PricesService
import ru.stepanovdn.trading.server.strategy.AInstrumentByFiatStrategy
import ru.stepanovdn.trading.server.strategy.AStrategy
import ru.stepanovdn.trading.server.strategy.StrategySelector
import java.time.temporal.ChronoUnit

/**
 * Сервис обрабатывает поток свечей
 * Принимает решение с помощью калькулятора о покупке/продаже инструмента в рамках включенных стратегий
 * Отправляет ордеры (покупка, продажа) на исполнение
 */
@Service
class PurchaseService (
    @Autowired val orderService: OrderService,
    @Autowired val strategySelector: StrategySelector,
    @Autowired val notificationService: NotificationService,
    @Autowired val calculator: CalculatorInstrumentByFiatService,
    @Autowired val pricesService: PricesService
) {
    private val log = LoggerFactory.getLogger(javaClass)

    /**
     * Обработчик новой свечи по всем стратегиям
     * Запускает параллельно обработку свечи по каждой стратегии
     * Не выбрасывает исключение, логирует ошибку
     *
     * @param candle
     */
    @Synchronized
    fun observeCandleNoThrow(candle: Candle) {
        try {
            log.debug("Observe candle event: {}", candle)
            pricesService.updatePrice(candle.figi!!, candle.closingPrice!!)
            val strategies: List<AInstrumentByFiatStrategy> = strategySelector.suitableByFigi(candle.figi)
            strategies.parallelStream().forEach { strategy -> observeCandle(candle, strategy) }
        } catch (e: Exception) {
            val msg = String.format("An error during observe new candle %s, %s", candle, e.message)
            log.error(msg, e)
            notificationService!!.sendMessage(msg)
        }
    }

    /**
     * Обработчик новой свечи по конкретной стратегии
     *
     * @param candle
     * @param strategy
     */
    private fun observeCandle(candle: Candle, strategy: AInstrumentByFiatStrategy) {
        // Ищем открытую позицию (ордер) по стратегии
        // - для стратегии instrumentByFiat нужен ордер по инструменту свечки (торгуется стратегия в разрезе инструмента)
        // - для стратегии instrumentByInstrument нужен ордер по любому инструменту (торгуется вся стратегия целиком)
        val figiForOrder: String = candle.figi!!
        val order: Order? = orderService!!.findActiveByFigiAndStrategy(figiForOrder, strategy)

        // Нет активной позиции по стратегии -> возможно можем купить, иначе продать
        if (order == null) {
            buy(candle, strategy)
        } else {
            sell(candle, order, strategy)
        }
    }

    /**
     * Покупка инструмента при выполнении условий стратегии
     *
     * @param candle
     * @param strategy
     */
    private fun buy(candle: Candle, strategy: AInstrumentByFiatStrategy) {
        // Проверяем, включена ли покупка по стратегии (выключаем в случае завершения торговли по стратегии)
        if (strategy.isOnlySell) {
            return
        }

        // Рассчитываем, нужно ли покупать
        val isShouldBuy = calculator!!.isShouldBuy(strategy, candle)
        if (!isShouldBuy) {
            return
        }

        // Проверяем задержку в торговле после продажи по stop loss
        if (strategy.type === AStrategy.Type.instrumentByFiat && strategy.delayBySL != null) {
            val finishedOrders: List<Order?> = orderService!!.findClosedByFigiAndStrategy(candle.figi, strategy)
            if (finishedOrders.isEmpty()) {
                val lastOrder: Order? = finishedOrders.last()
                if (ChronoUnit.SECONDS.between(
                        lastOrder!!.sellDateTime,
                        candle.dateTime
                    ) < strategy.delayBySL!!.seconds
                ) {
                    return
                }
            }
        }

        // Покупаем
        val order: Order = orderService!!.buy(candle, strategy)
        val msg = String.format(
            "Buy %s (%s), %s x%s, %s, %s. Wanted %s", order.figi,
            order.figiTitle, order.purchasePrice, order.lots, order.purchaseDateTime,
            order.strategy, candle.closingPrice
        )
        log.warn(msg)
        notificationService!!.sendMessage(msg)
    }

    /**
     * Продажа инструмента при выполнении условий стратегии
     *
     * @param candle
     * @param order
     * @param strategy
     */
    private fun sell(candle: Candle, order: Order, strategy: AInstrumentByFiatStrategy) {
        // Позиция есть, но пришла свечка с датой до покупки текущего ордера, так не должно быть
        var order1 = order
        if (order1.purchaseDateTime!!.isAfter(candle.dateTime)) {
            log.error("Was founded order before current candle date time: {}, {}", order1, candle)
            return
        }

        // Рассчитываем, нужно ли продавать
        val isShouldSell = calculator!!.isShouldSell(strategy, candle, order1.purchasePrice!!)
        if (!isShouldSell) {
            return
        }

        // Продаем
        order1 = orderService!!.sell(candle, strategy)!!
        val msg = String.format(
            "Sell %s (%s), %s x%s (%s), %s, %s. Wanted: %s", candle.figi,
            order1.figiTitle, order1.sellPrice, order1.lots, order1.sellProfit,
            order1.sellDateTime, order1.strategy, candle.closingPrice
        )
        log.warn(msg)
        notificationService!!.sendMessage(msg)
    }
}
